package arithm

import (
	"math/big"
	"testing"
)

func TestRationalAddWithInteger(t *testing.T) {
	// Test addition with Integer that results in a whole number
	ratVal := big.NewRat(5, 2) // 2.5
	x := (*Rational)(ratVal)
	y := Integer(2) // 2
	result := x.ADD(y)

	// When adding Rational with Integer, result should be a Rational
	// In this case, 2.5 + 2 = 4.5 = 9/2
	if result.Type() != RATIONAL_TYPE {
		t.Errorf("Expected result type %v, got %v", RATIONAL_TYPE, result.Type())
	}

	expectedRat := big.NewRat(9, 2) // 4.5
	if result.String() != expectedRat.String() {
		t.Errorf("Expected %v, got %v", expectedRat, result)
	}

	// Test addition with Integer that results in an integer
	ratVal2 := big.NewRat(4, 2) // 2
	x2 := (*Rational)(ratVal2)
	y2 := Integer(3)
	result2 := x2.ADD(y2)

	// When adding Rational with Integer that results in a whole number,
	// it should return an Integer or BigInteger depending on size
	expectedInt := Integer(5) // 2 + 3 = 5
	if result2.String() != expectedInt.String() {
		t.Errorf("Expected %v, got %v", expectedInt, result2)
	}
}

// TestRationalAddWithBigInteger tests Rational.ADD method with BigInteger values
func TestRationalAddWithBigInteger(t *testing.T) {
	// Test addition with BigInteger
	ratVal := big.NewRat(5, 2) // 2.5
	x := (*Rational)(ratVal)
	bigVal, _ := (new(big.Int)).SetString("12345678901234567890", 10)
	y := (*BigInteger)(bigVal)
	result := x.ADD(y)

	// When adding Rational with BigInteger, result should be a Rational
	ratBig := new(big.Rat).SetFrac(bigVal, big.NewInt(1))
	expectedRat := new(big.Rat).Add(ratVal, ratBig)
	if result.Type() != RATIONAL_TYPE {
		t.Errorf("Expected result type %v, got %v", RATIONAL_TYPE, result.Type())
	}

	if result.String() != expectedRat.String() {
		t.Errorf("Expected %v, got %v", expectedRat, result)
	}
}

// TestRationalAddWithRational tests Rational.ADD method with Rational values
func TestRationalAddWithRational(t *testing.T) {
	// Test addition with another Rational
	ratVal1 := big.NewRat(5, 2) // 2.5
	ratVal2 := big.NewRat(7, 4) // 1.75
	x := (*Rational)(ratVal1)
	y := (*Rational)(ratVal2)
	result := x.ADD(y)

	// When adding Rational with Rational, result should be a Rational
	// 5/2 + 7/4 = 10/4 + 7/4 = 17/4
	if result.Type() != RATIONAL_TYPE {
		t.Errorf("Expected result type %v, got %v", RATIONAL_TYPE, result.Type())
	}

	expectedRat := big.NewRat(17, 4) // 4.25
	if result.String() != expectedRat.String() {
		t.Errorf("Expected %v, got %v", expectedRat, result)
	}
}

// TestRationalAddWithReal tests Rational.ADD method with Real values
func TestRationalAddWithReal(t *testing.T) {
	// Test addition with Real
	ratVal := big.NewRat(5, 2) // 2.5
	x := (*Rational)(ratVal)
	y := Real(3.14)
	result := x.ADD(y)

	// When adding Rational with Real, it should delegate to Real.ADD
	// which should return a Real
	if result.Type() != REAL_TYPE {
		t.Errorf("Expected result type %v, got %v", REAL_TYPE, result.Type())
	}

	f, _ := ratVal.Float64()
	expected := Real(f + 3.14)
	if result.String() != expected.String() {
		t.Errorf("Expected %v, got %v", expected, result)
	}
}

// TestRationalAddWithComplex tests Rational.ADD method with Complex values
func TestRationalAddWithComplex(t *testing.T) {
	// Test addition with Complex
	ratVal := big.NewRat(5, 2) // 2.5
	x := (*Rational)(ratVal)
	y := Complex(3 + 2i)
	result := x.ADD(y)

	// When adding Rational with Complex, it should delegate to Complex.ADD
	// which should return a Complex
	if result.Type() != COMPLEX_TYPE {
		t.Errorf("Expected result type %v, got %v", COMPLEX_TYPE, result.Type())
	}

	f, _ := ratVal.Float64()
	expected := Complex(complex(f, 0) + complex128(y))
	if result.String() != expected.String() {
		t.Errorf("Expected %v, got %v", expected, result)
	}
}

// TestRationalSubWithInteger tests Rational.SUB method with Integer values
func TestRationalSubWithInteger(t *testing.T) {
	// Test subtraction with Integer
	ratVal := big.NewRat(5, 2) // 2.5
	x := (*Rational)(ratVal)
	y := Integer(2) // 2
	result := x.SUB(y)

	// When subtracting Integer from Rational, result should be a Rational
	// In this case, 2.5 - 2 = 0.5 = 1/2
	if result.Type() != RATIONAL_TYPE {
		t.Errorf("Expected result type %v, got %v", RATIONAL_TYPE, result.Type())
	}

	expectedRat := big.NewRat(1, 2) // 0.5
	if result.String() != expectedRat.String() {
		t.Errorf("Expected %v, got %v", expectedRat, result)
	}

	// Test subtraction that results in an integer
	ratVal2 := big.NewRat(4, 2) // 2
	x2 := (*Rational)(ratVal2)
	y2 := Integer(2)
	result2 := x2.SUB(y2)

	// When subtracting Integer from Rational that results in a whole number,
	// it should return an Integer
	expectedInt := Integer(0) // 2 - 2 = 0
	if result2.String() != expectedInt.String() {
		t.Errorf("Expected %v, got %v", expectedInt, result2)
	}
}

// TestRationalSubWithBigInteger tests Rational.SUB method with BigInteger values
func TestRationalSubWithBigInteger(t *testing.T) {
	// Test subtraction with BigInteger
	ratVal := big.NewRat(5, 2) // 2.5
	x := (*Rational)(ratVal)
	bigVal, _ := (new(big.Int)).SetString("12345678901234567890", 10)
	y := (*BigInteger)(bigVal)
	result := x.SUB(y)

	// When subtracting BigInteger from Rational, result should be a Rational
	ratBig := new(big.Rat).SetFrac(bigVal, big.NewInt(1))
	expectedRat := new(big.Rat).Sub(ratVal, ratBig)
	if result.Type() != RATIONAL_TYPE {
		t.Errorf("Expected result type %v, got %v", RATIONAL_TYPE, result.Type())
	}

	if result.String() != expectedRat.String() {
		t.Errorf("Expected %v, got %v", expectedRat, result)
	}
}

// TestRationalSubWithRational tests Rational.SUB method with Rational values
func TestRationalSubWithRational(t *testing.T) {
	// Test subtraction with another Rational
	ratVal1 := big.NewRat(5, 2) // 2.5
	ratVal2 := big.NewRat(7, 4) // 1.75
	x := (*Rational)(ratVal1)
	y := (*Rational)(ratVal2)
	result := x.SUB(y)

	// When subtracting Rational from Rational, result should be a Rational
	// 5/2 - 7/4 = 10/4 - 7/4 = 3/4
	if result.Type() != RATIONAL_TYPE {
		t.Errorf("Expected result type %v, got %v", RATIONAL_TYPE, result.Type())
	}

	expectedRat := big.NewRat(3, 4) // 0.75
	if result.String() != expectedRat.String() {
		t.Errorf("Expected %v, got %v", expectedRat, result)
	}
}

// TestRationalSubWithReal tests Rational.SUB method with Real values
func TestRationalSubWithReal(t *testing.T) {
	// Test subtraction with Real
	ratVal := big.NewRat(5, 2) // 2.5
	x := (*Rational)(ratVal)
	y := Real(3.14)
	result := x.SUB(y)

	// When subtracting Real from Rational, it should delegate to Real.ADD with negated value
	// which should return a Real
	if result.Type() != REAL_TYPE {
		t.Errorf("Expected result type %v, got %v", REAL_TYPE, result.Type())
	}

	f, _ := ratVal.Float64()
	expected := Real(f - 3.14)
	if result.String() != expected.String() {
		t.Errorf("Expected %v, got %v", expected, result)
	}
}

// TestRationalSubWithComplex tests Rational.SUB method with Complex values
func TestRationalSubWithComplex(t *testing.T) {
	// Test subtraction with Complex
	ratVal := big.NewRat(5, 2) // 2.5
	x := (*Rational)(ratVal)
	y := Complex(3 + 2i)
	result := x.SUB(y)

	// When subtracting Complex from Rational, it should delegate to Complex.ADD with negated value
	// which should return a Complex
	if result.Type() != COMPLEX_TYPE {
		t.Errorf("Expected result type %v, got %v", COMPLEX_TYPE, result.Type())
	}

	f, _ := ratVal.Float64()
	expected := Complex(complex(f, 0) - complex128(y))
	if result.String() != expected.String() {
		t.Errorf("Expected %v, got %v", expected, result)
	}
}

// TestRationalMulWithInteger tests Rational.MUL method with Integer values
func TestRationalMulWithInteger(t *testing.T) {
	// Test multiplication with Integer
	ratVal := big.NewRat(5, 2) // 2.5
	x := (*Rational)(ratVal)
	y := Integer(4)
	result := x.MUL(y)

	// When multiplying Rational with Integer, result should be a Rational or Integer
	// In this case, 5/2 * 4 = 20/2 = 10, which should be converted to Integer
	if result.Type() != INTEGER_TYPE {
		t.Errorf("Expected result type %v, got %v", INTEGER_TYPE, result.Type())
	}

	expectedInt := Integer(10) // 10
	if result.String() != expectedInt.String() {
		t.Errorf("Expected %v, got %v", expectedInt, result)
	}

	// Test multiplication that results in a non-integer
	ratVal2 := big.NewRat(4, 6) // 2/3
	x2 := (*Rational)(ratVal2)
	y2 := Integer(2)
	result2 := x2.MUL(y2)

	// When multiplying Rational with Integer that results in a non-integer,
	// it should remain a Rational
	if result2.Type() != RATIONAL_TYPE {
		t.Errorf("Expected result type %v, got %v", RATIONAL_TYPE, result2.Type())
	}

	expectedRat := big.NewRat(4, 3) // 4/3
	if result2.String() != expectedRat.String() {
		t.Errorf("Expected %v, got %v", expectedRat, result2)
	}
}

// TestRationalMulWithBigInteger tests Rational.MUL method with BigInteger values
func TestRationalMulWithBigInteger(t *testing.T) {
	// Test multiplication with BigInteger
	ratVal := big.NewRat(5, 2) // 2.5
	x := (*Rational)(ratVal)
	bigVal, _ := (new(big.Int)).SetString("12345678901234567891", 10) // Odd number
	y := (*BigInteger)(bigVal)
	result := x.MUL(y)

	// When multiplying Rational with BigInteger, result should be a Rational
	// since 5/2 * odd_number will not be a whole number
	ratBig := new(big.Rat).SetFrac(bigVal, big.NewInt(1))
	expectedRat := new(big.Rat).Mul(ratVal, ratBig)

	// Check that the result type is Rational
	if result.Type() != RATIONAL_TYPE {
		t.Errorf("Expected result type %v, got %v", RATIONAL_TYPE, result.Type())
	}

	// Check if the result matches the expected value
	if result.String() != expectedRat.String() {
		t.Errorf("Expected %v, got %v", expectedRat, result)
	}
}

// TestRationalMulWithRational tests Rational.MUL method with Rational values
func TestRationalMulWithRational(t *testing.T) {
	// Test multiplication with another Rational that results in a non-integer
	ratVal1 := big.NewRat(5, 2) // 2.5
	ratVal2 := big.NewRat(3, 5) // 0.6
	x := (*Rational)(ratVal1)
	y := (*Rational)(ratVal2)
	result := x.MUL(y)

	// When multiplying Rational with Rational, result should be a Rational
	// 5/2 * 3/5 = 15/10 = 3/2
	if result.Type() != RATIONAL_TYPE {
		t.Errorf("Expected result type %v, got %v", RATIONAL_TYPE, result.Type())
	}

	expectedRat := big.NewRat(3, 2) // 3/2
	if result.String() != expectedRat.String() {
		t.Errorf("Expected %v, got %v", expectedRat, result)
	}

	// Test multiplication with another Rational that results in an integer
	ratVal3 := big.NewRat(4, 2) // 2
	ratVal4 := big.NewRat(6, 3) // 2
	x2 := (*Rational)(ratVal3)
	y2 := (*Rational)(ratVal4)
	result2 := x2.MUL(y2)

	// When multiplying Rational with Rational that results in a whole number,
	// it should return an Integer or BigInteger
	expectedInt := Integer(4) // 2 * 2 = 4
	if result2.String() != expectedInt.String() {
		t.Errorf("Expected %v, got %v", expectedInt, result2)
	}
}

// TestRationalMulWithReal tests Rational.MUL method with Real values
func TestRationalMulWithReal(t *testing.T) {
	// Test multiplication with Real
	ratVal := big.NewRat(5, 2) // 2.5
	x := (*Rational)(ratVal)
	y := Real(3.14)
	result := x.MUL(y)

	// When multiplying Rational with Real, it should delegate to Real.MUL
	// which should return a Real
	if result.Type() != REAL_TYPE {
		t.Errorf("Expected result type %v, got %v", REAL_TYPE, result.Type())
	}

	f, _ := ratVal.Float64()
	expected := Real(f * 3.14)
	if result.String() != expected.String() {
		t.Errorf("Expected %v, got %v", expected, result)
	}
}

// TestRationalMulWithComplex tests Rational.MUL method with Complex values
func TestRationalMulWithComplex(t *testing.T) {
	// Test multiplication with Complex
	ratVal := big.NewRat(5, 2) // 2.5
	x := (*Rational)(ratVal)
	y := Complex(3 + 2i)
	result := x.MUL(y)

	// When multiplying Rational with Complex, it should delegate to Complex.MUL
	// which should return a Complex
	if result.Type() != COMPLEX_TYPE {
		t.Errorf("Expected result type %v, got %v", COMPLEX_TYPE, result.Type())
	}

	f, _ := ratVal.Float64()
	expected := Complex(complex(f, 0) * complex128(y))
	if result.String() != expected.String() {
		t.Errorf("Expected %v, got %v", expected, result)
	}
}

// TestRationalDivWithInteger tests Rational.DIV method with Integer values
func TestRationalDivWithInteger(t *testing.T) {
	// Test division with Integer
	ratVal := big.NewRat(10, 3) // 10/3
	x := (*Rational)(ratVal)
	y := Integer(5)
	result := x.DIV(y)

	// When dividing Rational by Integer, result should be a Rational
	// In this case, 10/3 / 5 = 10/15 = 2/3
	if result.Type() != RATIONAL_TYPE {
		t.Errorf("Expected result type %v, got %v", RATIONAL_TYPE, result.Type())
	}

	expectedRat := big.NewRat(2, 3) // 2/3
	if result.String() != expectedRat.String() {
		t.Errorf("Expected %v, got %v", expectedRat, result)
	}

	// Test division that results in an integer
	ratVal2 := big.NewRat(10, 2) // 5
	x2 := (*Rational)(ratVal2)
	y2 := Integer(5)
	result2 := x2.DIV(y2)

	// When dividing Rational by Integer that results in a whole number,
	// it should return an Integer
	expectedInt := Integer(1) // 5 / 5 = 1
	if result2.String() != expectedInt.String() {
		t.Errorf("Expected %v, got %v", expectedInt, result2)
	}
}

// TestRationalDivWithBigInteger tests Rational.DIV method with BigInteger values
func TestRationalDivWithBigInteger(t *testing.T) {
	// Test division with BigInteger
	ratVal := big.NewRat(10, 3) // 10/3
	x := (*Rational)(ratVal)
	bigVal, _ := (new(big.Int)).SetString("12345678901234567890", 10)
	y := (*BigInteger)(bigVal)
	result := x.DIV(y)

	// When dividing Rational by BigInteger, result should be a Rational
	ratBig := new(big.Rat).SetFrac(bigVal, big.NewInt(1))
	expectedRat := new(big.Rat).Quo(ratVal, ratBig)
	if result.Type() != RATIONAL_TYPE {
		t.Errorf("Expected result type %v, got %v", RATIONAL_TYPE, result.Type())
	}

	if result.String() != expectedRat.String() {
		t.Errorf("Expected %v, got %v", expectedRat, result)
	}
}

// TestRationalDivWithRational tests Rational.DIV method with Rational values
func TestRationalDivWithRational(t *testing.T) {
	// Test division with another Rational that results in a non-integer
	ratVal1 := big.NewRat(10, 3) // 10/3
	ratVal2 := big.NewRat(5, 6)  // 5/6
	x := (*Rational)(ratVal1)
	y := (*Rational)(ratVal2)
	result := x.DIV(y)

	// When dividing Rational by Rational, result should be a Rational
	// 10/3 / 5/6 = 10/3 * 6/5 = 60/15 = 4
	// Since it's a whole number, it should be converted to Integer
	if result.Type() != INTEGER_TYPE {
		t.Errorf("Expected result type %v, got %v", INTEGER_TYPE, result.Type())
	}

	expectedInt := Integer(4) // 4
	if result.String() != expectedInt.String() {
		t.Errorf("Expected %v, got %v", expectedInt, result)
	}

	// Test division with another Rational that results in a non-integer
	ratVal3 := big.NewRat(5, 2) // 5/2
	ratVal4 := big.NewRat(3, 4) // 3/4
	x2 := (*Rational)(ratVal3)
	y2 := (*Rational)(ratVal4)
	result2 := x2.DIV(y2)

	// When dividing Rational by Rational that results in a non-integer,
	// it should remain a Rational
	if result2.Type() != RATIONAL_TYPE {
		t.Errorf("Expected result type %v, got %v", RATIONAL_TYPE, result2.Type())
	}

	expectedRat2 := big.NewRat(10, 3) // 5/2 / 3/4 = 5/2 * 4/3 = 20/6 = 10/3
	if result2.String() != expectedRat2.String() {
		t.Errorf("Expected %v, got %v", expectedRat2, result2)
	}
}

// TestRationalDivWithReal tests Rational.DIV method with Real values
func TestRationalDivWithReal(t *testing.T) {
	// Test division with Real
	ratVal := big.NewRat(5, 2) // 2.5
	x := (*Rational)(ratVal)
	y := Real(3.14)
	result := x.DIV(y)

	// When dividing Rational by Real, it should delegate to Real.DIV
	// which actually computes y.DIV(x) = y / x
	// So we're computing 3.14 / 2.5
	if result.Type() != REAL_TYPE {
		t.Errorf("Expected result type %v, got %v", REAL_TYPE, result.Type())
	}

	f, _ := ratVal.Float64()
	expected := Real(3.14 / f)
	if result.String() != expected.String() {
		t.Errorf("Expected %v, got %v", expected, result)
	}
}

// TestRationalDivWithComplex tests Rational.DIV method with Complex values
func TestRationalDivWithComplex(t *testing.T) {
	// Test division with Complex
	ratVal := big.NewRat(5, 2) // 2.5
	x := (*Rational)(ratVal)
	y := Complex(3 + 2i)
	result := x.DIV(y)

	// When dividing Rational by Complex, it should delegate to Complex.DIV
	// which actually computes y.DIV(x) = y / x
	// So we're computing (3 + 2i) / 2.5
	if result.Type() != COMPLEX_TYPE {
		t.Errorf("Expected result type %v, got %v", COMPLEX_TYPE, result.Type())
	}

	f, _ := ratVal.Float64()
	expected := Complex(complex128(y) / complex(f, 0))
	if result.String() != expected.String() {
		t.Errorf("Expected %v, got %v", expected, result)
	}
}
